/* Copyright (c) 2008 LepomisLab.com, Andrey Turkin
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "main.h"

#include "wx/file.h"
#include "wx/cmdline.h"

#include "ipc.h"
#include "NSPropertyList.h"
#include "NSKeyedUnarchiver.h"
#include "NSIBObjectData.h"
#include "NSWindow.h"

#include <iostream>

IMPLEMENT_APP_NO_MAIN(tmDialogApp)

wxArrayString tmDialogApp::utf8_args;

NSObject* main_window = NULL;

void NSRegisterTopLevelWindow(NSObject* _wnd)
{
	if (!main_window)
		main_window = _wnd;
}
static void close_main_window(NSObject* sender)
{
	if (main_window)
		sender->sendAction(main_window, wxT("performClose:"), NULL);
}
void close_main_window(void)
{
	close_main_window(main_window);
}

class NSOwner : public NSObject
{
public:
	NSOwner() : NSObject()
	{
	}
	void setParameters(NSDictionary* params)
	{
		_parameters = params;
	}

	~NSOwner() {}
protected:
	virtual wxObject* _getValue(const wxString& key)
	{
		if (key == wxT("parameters"))
			return _parameters;
		else
			return NSObject::_getValue(key);
	}
	virtual void _setValue(const wxString& key, wxObject* object)
	{
		if (key == wxT("parameters"))
		{
			NSDictionary* dict = wxDynamicCast(object, NSDictionary);
			wxASSERT(dict);
			_parameters = dict;
		}
		else
			NSObject::_setValue(key, object);
	}
	virtual void _callAction(const wxString& action, wxObject* arg, NSObject* caller)
	{
		if (action == wxT("returnArgument:"))
		{
			wxString argname(wxT("returnArgument"));
			_willValueChangedForKey(wxT("parameters"));
			_parameters->Insert(wxT("result"), new NSDictionary(&argname, arg));
			_didValueChangedForKey(wxT("parameters"));
			close_main_window(this); /* until I understand why the window got closed on MacOS */
		} else if (action == wxT("performButtonClick:"))
		{
			_willValueChangedForKey(wxT("parameters"));
			_parameters->Insert(wxT("returnButton"), caller->getValue(wxT("title")));
			_parameters->Insert(wxT("returnCode"), caller->getValue(wxT("tag")));
			_didValueChangedForKey(wxT("parameters"));
			close_main_window(this);
		} else
			NSObject::_callAction(action, arg, caller);
	}

private:
	NSDictionary* _parameters;
} *main_owner;

void tmDialogApp::update_parameters(wxString& params)
{
	wxObject *parameters = NSPropertyList::initWithString(params);
	wxASSERT(parameters);
	NSDictionary* _params = wxDynamicCast(parameters, NSDictionary);
	wxASSERT(_params);
	_parameters->Merge(_params);
}

wxString tmDialogApp::generate_token(void)
{
	/* service name should be file name so IPC would work both via DDE (which uses arbitrary strings) and Unix sockets */
	wxString service = wxFileName::CreateTempFileName(wxEmptyString);
	wxRemoveFile(service); /* delete file just created by CreateTempFileName as we only need temp name, not file */
#ifdef __WXMSW__
	service = wxFileName::FileName(service).GetName(); /* try to avoid bad things like spaces or backslashes */
#endif
	std::cout << service.utf8_str() << std::endl;
	std::cout.flush();
	return service;
}

bool tmDialogApp::OnInit()
{
	wxString params, filename, token;
	bool center = false, modal = false, isPlist = false;
	bool update_asynchronous = false, isToken = false, wait_asynchronous = false, close_asynchronous = false;
	asynchronous = quiet = false;
	_parameters = NULL;

	for(size_t i = 1; i < utf8_args.GetCount(); i++)
	{
		wxString param = utf8_args[i];
		wxString p2;
restart:
		if (param.StartsWith(wxT("--"), &p2))
		{
			wxASSERT(false);
		} else if (param.StartsWith(wxT("-"), &p2))
		{
			bool terminal = false;
			for(size_t j = 0; j < p2.Len(); j++)
			{
				switch(p2[j])
				{
				case _T('a'):
					asynchronous = true;
					break;
				case _T('c'):
					center = true;
					break;
				case _T('m'):
					modal = true;
					break;
				case _T('p'):
					terminal = true;
					isPlist = true;
					break;
				case _T('q'):
					quiet = true;
					break;
				case _T('t'):
					update_asynchronous = true;
					terminal = true;
					isToken = true;
					break;
				case _T('w'):
					wait_asynchronous = true;
					terminal = true;
					isToken = true;
					break;
				case _T('x'):
					close_asynchronous = true;
					terminal = true;
					isToken = true;
					break;
				default:
					wxASSERT(false);
					break;
				}
				if (terminal)
				{
					param = p2.substr(j+1);
					if (!param.empty())
						goto restart;
				}
			}
		} else if (isPlist)
		{
			isPlist = false;
			params = param;
		} else if (isToken)
		{
			isToken = false;
			token = param;
		} else if (filename.IsEmpty())
		{
			filename = param;
		} else
		{
			std::cerr << param;
			wxASSERT(false);
		}
	}

	if (wait_asynchronous)
	{
		wxConnectionBase* cb = ipc_client_connect(token);
		ipc_wait_result(cb);
		return true; /* run without any window until server advises us */
	}

	if (close_asynchronous)
	{
		static const wxString wxClose = _T("close");
		wxConnectionBase* cb = ipc_client_connect(token);
		cb->Execute(wxClose);
		cb->Disconnect();
		ipc_close_client();
		return false;
	}

//#ifdef __WXDEBUG__
#if 0
	if (params.IsEmpty())
	{
		wxFile params_file;
		params_file.Open(wxT("c:\\_params"));
		size_t length = params_file.Length();
		wxUint8 *data = new wxUint8[length];
		params_file.Read(data, length);
		params_file.Close();
		params = wxConvUTF8.cMB2WC((const char *)data, length, NULL);
	}
#else
	if (params.IsEmpty())
	{
		std::string a;
		while(!std::cin.eof())
		{
			char buf[256] = {0,};
			std::cin.read(buf, sizeof(buf)-1);
			buf[std::cin.gcount()] = 0;
			a.append(buf);
		}
		params = wxConvUTF8.cMB2WC((const char *)a.c_str(), a.length(), NULL);
	}
#endif

#ifdef __WXDEBUG__
	if (filename.IsEmpty())
	{
		filename = wxT("c:\\");
	}
#endif

	params.Replace(wxT("\\\""), wxT("\""));   // work around wxWidgets bug(?) - it does not remove escapes from command line - e.g. <a="b">
	filename.Replace(wxT("\\\""), wxT("\"")); // transforms to "<a=\"b\">" by Windows and then gets transformed to <a=\"b\"> by wxWidgets
	                                          // This hack should not hurt (unless there _is_ \" sequence in original XML passed to tm_dialog)

	wxASSERT(!params.IsEmpty());

	if (update_asynchronous)
	{
		wxConnectionBase* cb = ipc_client_connect(token);
		cb->Poke(DialogParam, (wxChar*)params.data(), (int)(params.Len()*sizeof(wxChar)));
		cb->Disconnect();
		ipc_close_client();
		return false;
	}

	wxASSERT(!filename.IsEmpty());

	wxObject *parameters = NSPropertyList::initWithString(params);
	wxASSERT(parameters);
	_parameters = wxDynamicCast(parameters, NSDictionary);
	wxASSERT(_parameters);

	filename.Append(wxT("\\keyedobjects.nib"));
	wxFile file;
	if (!file.Open(filename))
	{
		wxASSERT(false);
		std::cout << "Cannot open NIB";
		return false;
	}
	size_t length = file.Length();
	wxUint8 *data = new wxUint8[length];
	file.Read(data, length);
	file.Close();
	wxObject *plist = NSPropertyList::initWithData(data, length);
	wxASSERT(plist);
	delete []data;

	if (asynchronous) /* need to spawn itself to unblock parent, and there is no platform-independent way (wxExecute just sucks!) */
	{
#ifdef __WXMSW__
		static const wxString env_name = wxT("E_DIALOG_TOKEN_NAME");
		if (!wxGetEnv(env_name, &token))
		{
			token = generate_token();
			wxSetEnv(env_name, token);
			size_t argc = utf8_args.size();
			char ** argv = new char *[argc];
			size_t len = 0;
			for(size_t i = 0; i < argc;i++)
			{
				argv[i] = strdup(utf8_args[i].utf8_str().data());
				len += 2*strlen(argv[i]) + 3; /* naive approach for quoting */
			}
			char *args  = new char[len+1];
			char *argp = args;
			for(size_t i = 0; i < argc;i++)
			{
				*(argp++) = '"';
				for(char *p = argv[i]; *p ; p++)
				{
					if (*p == '"' || *p == '\\') /* hope this works... */
						*(argp++) = '\\';
					*(argp++) = *p;
				}
				*(argp++) = '"';
				*(argp++) = ' ';
			}
			*(--argp) = 0;

			for(size_t i=0; i<argc;i++)
				free(argv[i]);
			delete argv;

			CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
			CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
			STARTUPINFOA si = {sizeof(si), };
			PROCESS_INFORMATION pi;
			exit(CreateProcessA(NULL, args, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi) ? 0 : 1);
		}
#else
		token = generate_token();
		close(0);
		close(1);
		int pid = fork();
		if (pid<0) /* fork failed */
			exit(1);
		if (pid>0) /* parent */
			exit(0);
		/* child can go on with work */
#endif
		ipc_create_server(token);
	}

	NSKeyedUnarchiver* keyed_unarchiver = new NSKeyedUnarchiver(plist);
	NSIBObjectData *root = wxDynamicCast(keyed_unarchiver->decodeObjectForKey(wxT("IB.objectdata")), NSIBObjectData);
 	wxASSERT(root);
	main_owner = new NSOwner();
	_parameters->Insert(wxT("controller"), main_owner);
	main_owner->setParameters(_parameters);
	root->instantiateWithOwner(main_owner, keyed_unarchiver);

	return true;
}

int tmDialogApp::OnExit()
{
	if (_parameters)
	{
		_parameters->Erase(wxT("controller"));
		wxString out = NSPropertyList::toPlist(_parameters);
		if(asynchronous)
		{
			ipc_close_server(out);
		} else
		{
			char* a = strdup(out.utf8_str().data());
			if (!quiet)
				std::cout << a;
			free(a);
		}
	}
	return 0;
}


int main(int argc, char* argv[])
{
	// To be compatible with cygwin, we want to avoid lf->crlf conversion
	_setmode( _fileno( stdout ), _O_BINARY );

	// We want to keep the ansi versions for the arguments so
	// that we can convert them from utf8.
	for (int i = 0; i < argc; ++i) 
	{
		tmDialogApp::utf8_args.Add(wxString(argv[i], wxConvUTF8));
	}

	// Start by initializing wxWidgets.
	// this is needed as we do not have a main wxApp class.
	wxEntry(::GetModuleHandle(NULL));
	
	return 0;
}
